<?php

declare(strict_types=1);

namespace App\Command;

use App\Model\Record;
use App\Model\RecordsFailure;
use App\Model\RecordsTemporary;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Hyperf\Command\Command as HyperfCommand;
use Hyperf\Command\Annotation\Command;
use Hyperf\DbConnection\Db;
use Hyperf\Guzzle\ClientFactory;
use Hyperf\Guzzle\CoroutineHandler;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Redis\Redis;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Coroutine;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;

/**
 * @Command
 */
#[Command]
class poster extends HyperfCommand
{
//    #[Inject]
//    protected QueueService $service;

    /**
     * 尿大夫的项目ID与医院的项目代码的映射
     * 以下为医院提供的项目代码
     * 1、抗坏血酸（项目代码：VC）
     * 2、白细胞（项目代码：LEU）
     * 3、葡萄糖（项目代码：GLU）
     * 4、微量白蛋白（项目代码：MA）
     * 5、胆红素（项目代码：BIL）
     * 6、酸碱度（项目代码：PH）
     * 7、比重（项目代码：SG）
     * 8、酮体（项目代码：KET）
     * 9、蛋白质（项目代码：PRO）
     * 10、亚硝酸盐（项目代码：NIT）
     * 11、尿胆原（项目代码：URO）
     * 12、肌酐（项目代码：CRE）
     * 13、潜血（项目代码：BLD）
     * 14、尿钙（项目代码：CA）
     */
    const PROJECT_MAP = [
        '23' => 'MA', //微量白蛋白(MA)
        '24' => 'CRE', //肌酐(CR)
        '25' => 'CA', //尿钙(Ca)
        '26' => 'PRO', //蛋白质(PRO)
        '27' => 'BLD', //潜血(BLD)
        '28' => 'PH', //酸碱度(PH)
        '29' => 'SG', //比重(SG)
        '30' => 'VC', //抗坏血酸(VC)
        '34' => 'BIL', //胆红素(BIL)
        '35' => 'URO', //尿胆原(URO)
        '36' => 'GLU', //葡萄糖(GLU)
        '37' => 'KET', //酮体(KET)
        '38' => 'LEU', //白细胞(LEU)
        '39' => 'NIT', //亚硝酸盐(NIT)
    ];

    /**
     * @var ContainerInterface
     */
    protected ContainerInterface $container;

    /**
     * http工厂类
     * @var ClientFactory
     */
    protected ClientFactory $clientFactory;

    /**
     * @var Client
     */
    protected $client;

    /**
     * 日志
     * @var LoggerInterface
     */
    protected LoggerInterface $logger;

    /**
     * @param ContainerInterface $container
     * @param ClientFactory $clientFactory
     * @param LoggerFactory $loggerFactory
     */
    public function __construct(ContainerInterface $container, ClientFactory $clientFactory, LoggerFactory $loggerFactory)
    {
        $this->container = $container;

        $this->clientFactory = $clientFactory;

        $this->logger = $loggerFactory->get('log', 'poster');

        parent::__construct('command:poster');
    }

    /**
     * @return void
     */
    public function configure()
    {
        parent::configure();
        $this->setDescription('请求内网HTTP客户端');
    }

    /**
     * @return mixed
     */
    public function handle()
    {
        // $client 为协程化的 GuzzleHttp\Client 对象
        $this->client = $this->clientFactory->create([
            'base_uri' => env('HOST'),
            'handler' => HandlerStack::create(new CoroutineHandler()),
            'timeout' => 5,
        ]);

        while (true) {
            $this->send();
        }
    }

    /**
     * 数据推送
     * @return void
     */
    public function send()
    {
        $sendType = env('PUSH_METHOD');
        if ($sendType == 'standard') {
            $this->sendWholeItem();
        } else {
            $this->sendSingleItem();
        }
    }

    /**
     * 每次推送所有项
     * @return void|null
     */
    public function sendWholeItem()
    {
        try {
            /**
             * @var RecordsTemporary $recordTemporary
             */
            $recordTemporary = RecordsTemporary::query()
                ->orderBy('send_count')
                ->first();

            if (empty($recordTemporary)) {
                Coroutine::sleep(1);
                return null;
            }

            //提取同一条检测记录的14个指标结果
            $recordTemps =  RecordsTemporary::query()
                ->orderBy('send_count')
                ->where('bar_code', $recordTemporary->bar_code)
                ->limit(14)
                ->get();

            unset($recordTemporary);

            Db::beginTransaction();

            foreach ($recordTemps as $recordTemporary) {
                $model = new Record();
                $model->record_id = $recordTemporary->record_id;
                $model->category_id = $recordTemporary->category_id;
                $model->standard_detail_id = $recordTemporary->standard_detail_id;
                $model->index = $recordTemporary->index;
                $model->cname = $recordTemporary->cname;
                $model->category_unit = $recordTemporary->category_unit;
                $model->status = $recordTemporary->status;
                $model->result = $recordTemporary->result;
                $model->project_id = $recordTemporary->project_id;
                $model->device_id = $recordTemporary->device_id;
                $model->bar_code = $recordTemporary->bar_code;
                $model->created_at = time();
                $model->save();

                $recordTemporary->delete();
            }

            Db::commit();
        } catch (\Throwable $e) {
            Db::rollBack();
            $this->logger->error("handle poster exception:" . $e->getMessage());

            foreach ($recordTemps as $recordTemporary) {
                //推送失败后处理
                $this->handleFailure($recordTemporary);
            }

            Coroutine::sleep(1);
        }
    }

    /**
     * 每次推送单项
     * @return void
     */
    public function sendSingleItem()
    {
        try {
            /**
             * @var RecordsTemporary $recordTemporary
             */
            $recordTemporary = RecordsTemporary::query()
                ->orderBy('send_count')
                ->first();
            if (empty($recordTemporary)) {
                Coroutine::sleep(1);
                return null;
            }

            $params = [
                'DeviceNames' => '吴试纸尿液检测', //设备名称
                'SampleNo' => $recordTemporary->bar_code, //样本编号
                'ItemName' => self::PROJECT_MAP[$recordTemporary->category_id], //项目代码
                'ItemValue' => $recordTemporary->result, //项目值
                //以下参数为非必填
                'ItemUnit' => $recordTemporary->category_unit, //项目单位
                'ItemRange' => $recordTemporary->index, //项目参考范围
                'ItemFlag' => '', //结果备注
            ];

            $response = $this->client->post(env('HOST'), [
                'headers' => [
                    'Content-Type' => 'application/json; charset=utf-8',
                    'Accept' => 'application/json',
                ],
                'body' => json_encode($params, JSON_UNESCAPED_UNICODE)
            ]);

            $body = $response->getBody();
            if (!$body) {
                throw new \Exception('client request fail:' . json_encode($body, JSON_UNESCAPED_UNICODE));
            }
            $data = json_decode($body->getContents(), true);
            if (empty($data)) {
                $msg = 'request params:' . json_encode($params, JSON_UNESCAPED_UNICODE) . ' response data:' . json_encode($body->getContents(), JSON_UNESCAPED_UNICODE);
                throw new \Exception('client response data error:' . $msg);
            }
            if (!isset($data['data'])) {
                $msg = 'request params:' . json_encode($params, JSON_UNESCAPED_UNICODE) . ' response data:' . json_encode($data, JSON_UNESCAPED_UNICODE);
                throw new \Exception('client response is not set data:' . $msg);
            }
            if ((int)$data['data']['num'] < 0) {
                $msg = 'request params:' . json_encode($params, JSON_UNESCAPED_UNICODE) . ' response data:' . json_encode($data, JSON_UNESCAPED_UNICODE);
                throw new \Exception('client response status fail:' . $msg);
            }

            $this->logger->info('推送成功:' . json_encode($params, JSON_UNESCAPED_UNICODE) . ' 返回结果:' . json_encode($data, JSON_UNESCAPED_UNICODE));

            Db::beginTransaction();

            $model = new Record();
            $model->record_id = $recordTemporary->record_id;
            $model->category_id = $recordTemporary->category_id;
            $model->standard_detail_id = $recordTemporary->standard_detail_id;
            $model->index = $recordTemporary->index;
            $model->cname = $recordTemporary->cname;
            $model->category_unit = $recordTemporary->category_unit;
            $model->status = $recordTemporary->status;
            $model->result = $recordTemporary->result;
            $model->project_id = $recordTemporary->project_id;
            $model->device_id = $recordTemporary->device_id;
            $model->bar_code = $recordTemporary->bar_code;
            $model->created_at = time();
            $model->save();

            $recordTemporary->delete();

            Db::commit();
        } catch (\Throwable $e) {
            Db::rollBack();
            $this->logger->error("handle poster exception:" . $e->getMessage());

            //推送失败后处理
            $this->handleFailure($recordTemporary);

            Coroutine::sleep(1);
        }
    }

    /**
     * 推送失败后处理
     * @param $recordTemporary RecordsTemporary
     * @return void
     */
    private function handleFailure($recordTemporary)
    {
        try {
            Db::beginTransaction();

            //如果超过两小时都没推送成功，归档进失败表里，否则增加记录失败次数
            if ($recordTemporary->created_at + 7200 < time()) {
                $model = new RecordsFailure();
                $model->record_id = $recordTemporary->record_id;
                $model->category_id = $recordTemporary->category_id;
                $model->standard_detail_id = $recordTemporary->standard_detail_id;
                $model->index = $recordTemporary->index;
                $model->cname = $recordTemporary->cname;
                $model->category_unit = $recordTemporary->category_unit;
                $model->status = $recordTemporary->status;
                $model->result = $recordTemporary->result;
                $model->project_id = $recordTemporary->project_id;
                $model->device_id = $recordTemporary->device_id;
                $model->bar_code = $recordTemporary->bar_code;
                $model->created_at = time();
                $model->save();

                $recordTemporary->delete();
            } else {
                $recordTemporary = RecordsTemporary::query()->find($recordTemporary->id);
                $recordTemporary->send_count++;
                $recordTemporary->save();
            }

            Db::commit();
        } catch (\Throwable $e) {
            Db::rollBack();
            $this->logger->error("handle poster failure exception:" . $e->getMessage());
        }

        //放入错误报告队列
        try {
            $container = ApplicationContext::getContainer();
            $redis = $container->get(Redis::class);
            $redis->lPush('errorReport', json_encode($recordTemporary->toArray(), JSON_UNESCAPED_UNICODE));
        } catch (\Throwable $e) {
            $this->logger->error("handle poster failure exception:" . $e->getMessage());
        }
    }
}
